SQlite源码分析

重要代码解析

在mem5.c中有一个重要的结构体变量mem5,贯穿于文件的始末,包含了许多重要常量及数组。该结构体变量定义如下:

static SQLITE_WSD struct Mem5Global {
  int szAtom;
  int nBlock;
  u8 *zPool;
  sqlite3_mutex *mutex;
  u64 nAlloc;         /* Total number of calls to malloc */
  u64 totalAlloc;     /* Total of all malloc calls - includes internal frag */
  u64 totalExcess;    /* Total internal fragmentation */
  u32 currentOut;     /* Current checkout, including internal fragmentation */
  u32 currentCount;   /* Current number of distinct checkouts */
  u32 maxOut;         /* Maximum instantaneous currentOut */
  u32 maxCount;       /* Maximum instantaneous currentCount */
  u32 maxRequest;     /* Largest allocation (exclusive of internal frag) */
  int aiFreelist[LOGMAX+1];
  u8 *aCtrl;
} mem5;

其中包含了该内存分配器以字节为单位的最小可能分配szAtom,可用来分配的内存zPool等静态变量。Mem5使用了一个大小为31个字节的空闲链表aiFreelist[LOGMAX+1]来保存空闲的block,当下标值为 -1 表示是空链表,aiFreelist[0] 保存了大小为 mem5.szAtom个字节的空闲block ,aiFreelist[1] 保存了大小为 mem5.szAtom * 2 个字节的空闲block ,以此类推。结构体变量mem5中定义了aCtrl,aCtrl是一个字符数组,保存了每个block的控制信息,当前block的大小,以及是否checkout,等等,每个大小为szAtom的block 占一个位置;同样利用了最后的3个bit来做控制,因为每个块最小为8个字节,所以,最后的3个bit是空闲的,可以用来存储。

分配时,找到最接近szAtom的X2次幂大小的块大小iLogSize,然后查看aiFreeList链表中有没有对应大小的块,如果没有则向上找,直到找到一个空闲链表项为止,如果没有找到,则返回失败。如果找到了符合要求的项,则从free双向链表中卸下大小为iBin的块。这里的block有可能很大,所以要切分一下。如果iBin 比 iLogSize要大,则切分,切下一半 iBin / 2 的块放入空闲链表,然后一直切下去,.iBin / 4, iBin / 8 , ..... 直至大小 <= iLogSize 为止, 由于 iBinSize为 szAtom的2次幂,这里的一半一半的切分,总是成功的。切到最后满足要求的块,将这一项在aCtrl数组中对应的控制字节设置为iLogSize,iLogSize是szAtom的几次幂,所以是可以用1 - 32 这样的一个字节表达的范围来保存的。具体的代码实现在static void *memsys5MallocUnsafe(int nByte)中。

释放时,首先判断释放的地址的大小iLogSize,落在偶数倍还是奇数倍地址上,如果是奇数倍地址,则向前合并;如果是偶数倍地址,则向后合并。直到要合并的项不是空闲的,或者已经超出了整个的大小为止。unlink所有的,到最后再link进合并后的最大的空闲块。具体代码实现在static void memsys5FreeUnsafe(void *pOld)中。